#include "common.h"
#include "properties.h"
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>

typedef JNIIMPORT int(JNICALL *JLI_Launch_f)(int argc, char **argv,                 /* main argc, argc */
                                             int jargc, const char **jargv,         /* java args */
                                             int appclassc, const char **appclassv, /* app classpath */
                                             const char *fullversion,               /* full version defined */
                                             const char *dotversion,                /* UNUSED dot version defined */
                                             const char *pname,                     /* program name */
                                             const char *lname,                     /* launcher name */
                                             jboolean javaargs,                     /* JAVA_ARGS */
                                             jboolean cpwildcard,                   /* classpath wildcard*/
                                             jboolean javaw,                        /* windows-only javaw */
                                             jint ergo                              /* unused */
);

void start(Common &common) {
    // set app.home env
    common.setEnvVar(L"app.home", common.getExeDir());
    Properties &properties = Properties::getInstance();
    std::list<WString> javaHome = properties.getJavaHome();
    if (javaHome.empty()) {
        throw std::runtime_error("exe.javaHome is not set!");
    }

    JLI_Launch_f JLI_Launch = nullptr;
    SetDllDirectoryW(nullptr);
    WString jliDll;
    for (WString home : javaHome) {
        jliDll = home + L"\\bin\\jli.dll";
        if (common.isFileExists(jliDll)) {
            HMODULE module = LoadLibrary(jliDll.data());
            if (module) {
                JLI_Launch = (JLI_Launch_f)GetProcAddress(module, "JLI_Launch");
                if (JLI_Launch) {
                    break;
                }
            }
        }
    }

    if (JLI_Launch) {
        int exeArgc = 1;
        char **exeArgv = nullptr;
        int appArgc = 0;
        const char **appArgv = nullptr;

        std::list<WString> cmdArgs = common.getCommandLineArgs();
        bool defaultLaunch = false;
        if (cmdArgs.empty()) {
            std::list<WString> vmOptions = properties.getVmOptions();
            if (vmOptions.empty()) {
                defaultLaunch = true;
            } else {
                exeArgv = (char **)calloc(vmOptions.size() + 1, sizeof(char *));
                exeArgv[0] = _strdup(common.wstring2utf8(common.getExePath()).c_str());
                for (WString option : vmOptions) {
                    exeArgv[exeArgc] = _strdup(common.wstring2utf8(option).c_str());
                    exeArgc++;
                }

                std::list<WString> mainArgs = properties.getMainArgs();
                appArgv = (const char **)calloc(mainArgs.size(), sizeof(char *));
                for (WString argv : mainArgs) {
                    appArgv[appArgc++] = common.wstring2utf8(argv).c_str();
                }
            }
        } else {
            exeArgv = (char **)calloc(cmdArgs.size() + 1, sizeof(char *));
            exeArgv[0] = _strdup(common.wstring2utf8(common.getExePath()).c_str());
            for (WString argv : cmdArgs) {
                exeArgv[exeArgc] = _strdup(common.wstring2utf8(argv).c_str());
                exeArgc++;
            }
        }

        if (defaultLaunch) {
            WString jstartJar = common.getExeDir() + L"\\jstart.jar";
            if (common.isFileExists(jstartJar)) {
                exeArgc = 3;
                exeArgv = (char **)calloc(exeArgc, sizeof(char *));
                exeArgv[0] = _strdup(common.wstring2utf8(common.getExePath()).c_str());
                exeArgv[1] = "-jar";
                exeArgv[2] = _strdup(common.wstring2utf8(jstartJar).c_str());
            }
        }
        if (exeArgc < 3) {
            throw std::runtime_error("Arguments or config file is missing! "
                                     "You have three way to use the launcher: \n\n"
                                     "1. Pass arguments just as pass arguments to java.exe to this program "
                                     "eg: \"-jar PATH\\TO\\RUNNABLE.jar\";\n\n"
                                     "2. Copy RUNNABLE.jar to next to this program and rename to jstart.jar;\n\n"
                                     "3. Using jstart.properties file config launch properties (Recommended).\n");
        }
        JLI_Launch(exeArgc, exeArgv,     // main args
                   appArgc, appArgv,     // java args
                   0, NULL,              // app classpath
                   "", "",               // version info
                   "jstart", "jli_launch", // program name, launcher name
                   appArgc > 0,          // is java args
                   false,                // is classpath with wild card
                   false,                // is a start using javaw
                   0);
    } else {
#ifdef X64
        throw std::runtime_error("Load " + common.wstring2utf8(jliDll) +
                                 " failed!\n\nPlease check if it's a 64 bits jdk/jre!");
#else
        throw std::runtime_error("Load " + common.wstring2utf8(jliDll) +
                                 " failed!\n\nPlease check if it's a 32 bits jdk/jre!");
#endif
    }
}

// 控制台程序入口，方便调试
int main(int argc, char *argv[]) {
    Common &common = Common::getInstance();
    try {
        start(common);
    } catch (std::runtime_error &e) {
        std::string msg = e.what();
        common.showMessageBox(true, WString(msg.begin(), msg.end()));
        return 1;
    }
    return 0;
}

// windows 窗口程序的入口函数
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
    Common &common = Common::getInstance();
    try {
        start(common);
    } catch (std::runtime_error &e) {
        std::string msg = e.what();
        common.showMessageBox(true, WString(msg.begin(), msg.end()));
        return 1;
    }
    return 0;
}
